package com.jacky.util;

import android.content.Context;
import android.graphics.ImageFormat;
import android.graphics.Rect;
import android.media.Image;
import android.util.Size;
import android.view.Gravity;

import java.nio.ByteBuffer;

public class OcrUtil {

    /**
     * dp转换成px
     */
    public static int dip2px(Context context, float dpValue) {
        if(context == null)return (int)dpValue;
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }


    /**
     * 将Y:U:V == 4:2:2的数据转换为nv21
     *
     * @param y      Y 数据
     * @param u      U 数据
     * @param v      V 数据
     * @param nv21   生成的nv21，需要预先分配内存
     * @param stride 步长
     * @param height 图像高度
     */
    public static void yuv422ToYuv420sp(byte[] y, byte[] u, byte[] v, byte[] nv21, int stride, int height) {
        System.arraycopy(y, 0, nv21, 0, y.length);
        // 注意，若length值为 y.length * 3 / 2 会有数组越界的风险，需使用真实数据长度计算
        int length = y.length + u.length / 2 + v.length / 2;
        int uIndex = 0, vIndex = 0;
        for (int i = stride * height; i < length; i += 2) {
            nv21[i] = v[vIndex];
            nv21[i + 1] = u[uIndex];
            vIndex += 2;
            uIndex += 2;
        }
    }

    /**
     * 将Y:U:V == 4:1:1的数据转换为nv21
     *
     * @param y      Y 数据
     * @param u      U 数据
     * @param v      V 数据
     * @param nv21   生成的nv21，需要预先分配内存
     * @param stride 步长
     * @param height 图像高度
     */
    public static void yuv420ToYuv420sp(byte[] y, byte[] u, byte[] v, byte[] nv21, int stride, int height) {
        System.arraycopy(y, 0, nv21, 0, y.length);
        // 注意，若length值为 y.length * 3 / 2 会有数组越界的风险，需使用真实数据长度计算
        int length = y.length + u.length + v.length;
        int uIndex = 0, vIndex = 0;
        for (int i = stride * height; i < length; i++) {
            nv21[i] = v[vIndex++];
            nv21[i + 1] = u[uIndex++];
        }
    }

    public static final int YUV420P = 0;
    public static final int YUV420SP = 1;
    public static final int NV21 = 2;
    /***
     * 此方法内注释以640*480为例
     * 未考虑CropRect的
     * https://blog.csdn.net/liujun3512159/article/details/123413786
     */
    public static byte[] getBytesFromImageAsType(Image image,int type) {
        try {
            //获取源数据，如果是YUV格式的数据planes.length = 3
            //plane[i]里面的实际数据可能存在byte[].length <= capacity (缓冲区总大小)
            final Image.Plane[] planes = image.getPlanes();
            //数据有效宽度，一般的，图片width <= rowStride，这也是导致byte[].length <= capacity的原因
            // 所以我们只取width部分
            int width = image.getWidth();
            int height = image.getHeight();
            //此处用来装填最终的YUV数据，需要1.5倍的图片大小，因为Y U V 比例为 4:1:1
            byte[] yuvBytes = new byte[width * height *
                    ImageFormat.getBitsPerPixel(ImageFormat.YUV_420_888) / 8];
            OcrLogger.d("image w*h",width,height,"yuvBytes.length",yuvBytes.length
                    ,width * height * ImageFormat.getBitsPerPixel(ImageFormat.YUV_420_888) / 8
                    ,ImageFormat.getBitsPerPixel(ImageFormat.YUV_420_888));
            //目标数组的装填到的位置
            int dstIndex = 0;
            //临时存储uv数据的
            byte[] uBytes = new byte[width * height / 4];
            byte[] vBytes = new byte[width * height / 4];
            int uIndex = 0;
            int vIndex = 0;
            int pixelsStride, rowStride;
            for (int i = 0; i < planes.length; i++) {
                pixelsStride = planes[i].getPixelStride();
                rowStride = planes[i].getRowStride();
                ByteBuffer buffer = planes[i].getBuffer();
                //如果pixelsStride==2，一般的Y的buffer长度=640*480，UV的长度=640*480/2-1
                //源数据的索引，y的数据是byte中连续的，u的数据是v向左移以为生成的，两者都是偶数位为有效数据
                byte[] bytes = new byte[buffer.capacity()];
                OcrLogger.d("i",i,"pixels",pixelsStride,"row",rowStride,
                        "width",width,"height",height,
                        "buffer.capacity()",buffer.capacity(),"remain()",buffer.remaining());

                int srcIndex = 0;
                if (i == 0) {
                    //直接取出来所有Y的有效区域，也可以存储成一个临时的bytes，到下一步再copy
                    for (int j = 0; j < height; j++) {
                        System.arraycopy(bytes, srcIndex, yuvBytes, dstIndex, width);
                        srcIndex += rowStride;
                        dstIndex += width;
                    }
                } else if (i == 1) {
                    //根据pixelsStride取相应的数据
                    for (int j = 0; j < height / 2; j++) {
                        for (int k = 0; k < width / 2; k++) {
                            uBytes[uIndex++] = bytes[srcIndex];
                            srcIndex += pixelsStride;
                        }
                        if (pixelsStride == 2) {
                            srcIndex += rowStride - width;
                        } else if (pixelsStride == 1) {
                            srcIndex += rowStride - width / 2;
                        }
                    }
                } else if (i == 2) {
                    //根据pixelsStride取相应的数据
                    for (int j = 0; j < height / 2; j++) {
                        for (int k = 0; k < width / 2; k++) {
                            vBytes[vIndex++] = bytes[srcIndex];
                            srcIndex += pixelsStride;
                        }
                        if (pixelsStride == 2) {
                            srcIndex += rowStride - width;
                        } else if (pixelsStride == 1) {
                            srcIndex += rowStride - width / 2;
                        }
                    }
                }
            }
            //根据要求的结果类型进行填充
            switch (type) {
                case YUV420P:
                    System.arraycopy(uBytes, 0, yuvBytes, dstIndex, uBytes.length);
                    System.arraycopy(vBytes, 0, yuvBytes, dstIndex + uBytes.length, vBytes.length);
                    break;
                case YUV420SP:
                    for (int i = 0; i < vBytes.length; i++) {
                        yuvBytes[dstIndex++] = uBytes[i];
                        yuvBytes[dstIndex++] = vBytes[i];
                    }
                    break;
                case NV21:
                    for (int i = 0; i < vBytes.length; i++) {
                        yuvBytes[dstIndex++] = vBytes[i];
                        yuvBytes[dstIndex++] = uBytes[i];
                    }
                    break;
            }
            return yuvBytes;
        } catch (final Exception e) {
//            if (image != null) {
//                image.close();
//            }
            OcrLogger.d(e);
        }
        return null;
    }

    public static  byte[] convertPlanes2NV21(int width, int height, ByteBuffer yPlane, ByteBuffer uPlane, ByteBuffer vPlane) {
        //尝试 plane[0] + plane[2] + plane[1]的最后一个元素  https://blog.csdn.net/qq_23069607/article/details/122698315
        int totalSize = width * height * 3 / 2;
        byte[] nv21Buffer = new byte[totalSize];
        int len = yPlane.capacity();
        yPlane.get(nv21Buffer, 0, len);
        vPlane.get(nv21Buffer, len, vPlane.capacity());
        byte lastValue = uPlane.get(uPlane.capacity() - 1);
        nv21Buffer[totalSize - 1] = lastValue;
        return nv21Buffer;
    }
    public static void onPreview2(byte[] y, byte[] uBytes, byte[] vBytes,byte[] nv21){
        System.arraycopy(y, 0, nv21, 0, y.length);
        int dstIndex = y.length-1;
        for (int i = 0; i < vBytes.length; i++) {
            nv21[dstIndex++] = vBytes[i];
            nv21[dstIndex++] = uBytes[i];
        }
    }

    /**
     * 使用该种方式，在OPPO Reno4 Pro5 5G中，图片保存出现 绿色的花屏
     * https://my.oschina.net/u/3970172/blog/3071603
     */
    public static byte[] onPreview(byte[] y, byte[] u, byte[] v,byte[] nv21, Size previewSize, int rowStride) {
        if (nv21 == null) {
            nv21 = new byte[rowStride * previewSize.getHeight() * 3 / 2];
        }
        OcrLogger.d("preview ",previewSize,rowStride,previewSize.getHeight(),y.length / u.length
                ,"nv21 length",rowStride * previewSize.getHeight() * 3 / 2);
        // 回传数据是YUV422
        if (y.length / u.length == 2) {
            OcrUtil.yuv422ToYuv420sp(y, u, v, nv21, rowStride, previewSize.getHeight());
        }
        // 回传数据是YUV420
        else if (y.length / u.length == 4) {
            OcrUtil.yuv420ToYuv420sp(y, u, v, nv21, rowStride, previewSize.getHeight());
        }
        return nv21;
    }

    /**
     *
     * @param rec 显示截取区域
     * @param angle 相机当前的旋转角度
     * @param gravity 相机控件在父控件中的位置：顶部、居中、底部，全屏
     * @param previewW 相机预览 宽
     * @param previewH 高
     * @param cameraViewW 相机控件 框
     * @param cameraViewH 高
     * @param viewW 相机控件父布局，相当于插件view占据宽高
     * @param viewH 高
     * @return 图片中对应的截取区域
     */
    public static Rect getViewRect(Rect rec, int angle, int gravity, int previewW, int previewH,
                                   int cameraViewW, int cameraViewH, int viewW, int viewH){
        int w = Math.max(viewW,viewH);
        int h = Math.min(viewW,viewH);
        float xs = (previewW / (float) w);
        float ys = (previewH / (float) h);

        int recH = rec.bottom - rec.top;
        int recW = rec.right - rec.left;
        int width;// = (int) ((rec.bottom - rec.top)*xs);
        int height;// = (int) ((rec.right - rec.left)*ys);
        Rect rect = new Rect();
        //angle = portraitToLandscape ? angle : 0;
        //’代表度，0‘表示摄像头真实方向，90’代表预览方向 90度，w代表宽度，h代表高度
        switch (angle){
            case 90:
                //0‘left = 90’top，0‘top=90’w-90‘right，90’w=0‘h
                //使用xs 还是ys，有0’的left、top决定
                rect.left = (int) (rec.top * xs);//(int) ((viewH() - rec.bottom) * xs);
                rect.top = (int) ((viewW - rec.right) * ys);//(int) (rec.left * ys);
                width = (int) (recH * xs);
                height = (int) (recW * ys);
                break;
            case 180:
                //0‘left = 180’w-180‘right，0‘top=180’h-180‘bottom，180’w=0‘w，180’h=0‘h
                rect.left = (int) ((viewW - rec.right) * xs);//(int) ((viewW - rec.right) * xs);
                rect.top = (int) ((viewH - rec.bottom) * ys);//(int) ((viewH() - rec.bottom) * ys);
                width = (int) (recW * xs);
                height = (int) (recH * ys);
                break;
            case 270:
                //0‘left = 270’h-270’bottom，0‘top=270‘left，270’w=0‘h，270’h=0‘w
                rect.left = (int) (((viewH - rec.bottom)) * xs);//(int) (rec.top* xs);
                rect.top = (int) (rec.left * ys);//(int) ((viewW - rec.right) * ys);
                width = (int) (recH * xs);
                height = (int) (recW * ys);
                break;
            case 0:
            default:
                //0‘left = 360‘left,0‘top=360‘top,360’w=0‘w，360’h=0‘
                rect.left = (int) (rec.left * xs);
                rect.top = (int) (rec.top * ys);
                width = (int) (recW * xs);
                height = (int) (recH * ys);
                break;
        }
        rect.right = rect.left + width;
        rect.bottom = rect.top + height;
        //因为摄像头画面居中了，导致摄像头预览区 跟getWidth控件区不一致，因此还有放大比例
        int cameraX = Math.max(cameraViewH,cameraViewW);
        int cameraY = Math.min(cameraViewH,cameraViewW);
        if(cameraViewH <= 0 || cameraViewW <= 0 || w <= 0 || h <= 0) return rect;
        int needDisW = (int) ((Math.max(recW,recH) *1f / cameraX) * previewW - rect.width());
        int needDisH = (int) ((Math.min(recW,recH) *1f / cameraY) * previewH - rect.height());

        float xss = cameraX * 1f / w;
        float yss = cameraY * 1f / h;
        //放大位置移动
        int disX = (int) ((w-cameraX)*xs/2);
        int disY = (int) ((h-cameraY)*ys/2);

        if(gravity == Gravity.CENTER){
            //居中
            rect.top = rect.top - needDisH/2;
            rect.bottom = rect.bottom + needDisH/2;
            rect.left = rect.left - needDisW/2;
            rect.right = rect.right + needDisW/2;
        }else if(gravity == Gravity.TOP){
            //预览区域 在图片 下移 区域有问题rect.left小了 right小了  上个版本也是同样问题
            rect.left = rect.left + needDisW/2;
            rect.right = rect.right + needDisW/2;
        }else if(gravity == Gravity.BOTTOM){
            //预览区域 在图片 上移   区域有问题rect.left大了 right大了
            rect.left = rect.left - needDisW/2;
            rect.right = rect.right - needDisW/2;
        }else return rect;
            //getCameraAngle 90 camera w*h 1080 1920 get w*h 1080 2210 getDisplayMetrics w*h 1080 2132
        // viewW w*h 1080 1920 pixel w*h 1080 2132 preview w*h 1920 1080 use w*h 2210 1080
        // ratio xss*yss 0.8687783 1.0 ratio xs*ys 0.8687783 1.0 use rect Rect(509, 2 - 1409, 1080)
        // use r Rect(442, 2 - 1476, 1080) overlay rect Rect(0, 586 - 1078, 1623)
//        DisplayMetrics dm = getResources().getDisplayMetrics();
        OcrLogger.d(
                "getCameraAngle",angle,
                "camera w*h",cameraViewW,cameraViewH,
                "viewW w*h",viewW,viewH,
//                "pixel w*h",dm.widthPixels,dm.heightPixels,
                "preview w*h",previewW,previewH,
                "use w*h",w,h,
                "ratio xss*yss",xss,yss,
                "ratio xs*ys",xs,ys,
                "distance x*y",disX,disY,
                "needDisW * needDisH",needDisW,needDisH,
                "use rect",rect,
                "overlay rect",rec);
        return rect;
    }
}
